package com.gskj.gulimall.seckill.service.impl;

import club.gskj.common.to.mq.SeckillOrderTO;
import club.gskj.common.utils.R;
import club.gskj.common.vo.MemberRespVo;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.gskj.gulimall.seckill.feign.CouponFeignService;
import com.gskj.gulimall.seckill.feign.ProductFeignService;
import com.gskj.gulimall.seckill.interceptor.UserLoginInterceptor;
import com.gskj.gulimall.seckill.service.SeckillService;
import com.gskj.gulimall.seckill.to.SeckillSkuRedisTO;
import com.gskj.gulimall.seckill.vo.SeckillSessionsWithSkusVO;
import com.gskj.gulimall.seckill.vo.SkuInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.cfg.defs.br.CPFDef;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import sun.rmi.runtime.Log;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 缓存秒杀活动前缀
     */
    public static final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";

    /**
     * 缓存商品前缀
     */
    public static final String SKU_KEY_CACHE_PREFIX = "seckill:skus";

    /***
     *  商品的库存信号量
     */
    public static final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+商品随机码

    @Override
    public void uploadSeckillSkuLatest3Days() {
        // 1.扫描数据库最近三天需要参与秒杀的商品

        R r = couponFeignService.getLates3DaysSession();
        if (r.getCode() == 0) {
            List<SeckillSessionsWithSkusVO> data = r.getData(new TypeReference<List<SeckillSessionsWithSkusVO>>() {
            });
            if (data != null) {
                // 将秒杀的商品放入缓存中
                // 1. 缓存活动信息
                saveSessionInfo(data);
                // 2. 缓存活动关联的商品信息
                saveSessionSkuInfo(data);
            }
        }
    }

    /**
     * 受保护资源熔断 回调方法
     */
    public List<SeckillSkuRedisTO> blockHandler(BlockException e) {
        log.error("getCurrentSeckillSkus被限流了,{}", e.getMessage());
        return null;
    }

    /**
     * @SentinelResource 配置自定义受保护的资源
     *  blockHandler 函数会在原方法被限流/降级/系统保护的时候调用，
     *  fallback 函数会针对所有类型的异常
     */
    @SentinelResource(value = "getCurrentSeckillSkus", blockHandler = "blockHandler")
    @Override
    public List<SeckillSkuRedisTO> getCurrentSeckillSkus() {
        // 1. 确定时间属于哪个秒杀场次
        // 1970开始的时间计数到现在的毫秒数
        long time = new Date().getTime();

        // 自定义受保护的资源
        try (Entry entry = SphU.entry("currentSeckillSkus")) {
            // 从缓存中查询所有保存秒杀场次的key值，截取字符串，判断是否与包含当前时间
            Set<String> keys = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
            for (String key : keys) {
                // redis存储的
//            "seckill:sessions:1618596000000_1618599600000"
                String replace = key.replace(SESSIONS_CACHE_PREFIX, "");
                // 获取开始时间 结束时间
                String[] s = replace.split("_");

                Long startTiem = Long.valueOf(s[0]);
                Long endTiem = Long.valueOf(s[1]);
                // 判断当前时间是否在 缓存的场次所包含的时间中
                if (time >= startTiem && time <= endTiem) {
                    // 获取当前缓存总的场次信息，以及对应的所有商品信息
                    List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);
                    // 获取之前绑定的hash
                    BoundHashOperations<String, String, String> hashOps =
                            stringRedisTemplate.boundHashOps(SKU_KEY_CACHE_PREFIX);
                    // 通过缓存中存储的场次值，查询对应的商品信息
                    List<String> lists = hashOps.multiGet(range);
                    if (null != lists) {
                        // 遍历list 重新映射成 TO对象
                        List<SeckillSkuRedisTO> collect = lists.stream().map(item -> {
                            SeckillSkuRedisTO seckillSkuRedisTO = JSON.parseObject((String) item, SeckillSkuRedisTO.class);
                            return seckillSkuRedisTO;
                        }).collect(Collectors.toList());
                        return collect;
                    }
                    break;
                }
            }
        } catch (BlockException e) {
            log.error("自定义资源限流,{}", e.getMessage());
        }
        return null;
    }

    @Override
    public SeckillSkuRedisTO getSkuSeckillInfo(Long skuId) {
        // 找到所有缓存中存放的商品秒杀信息
        BoundHashOperations<String, String, String> opsHash = stringRedisTemplate.boundHashOps(SKU_KEY_CACHE_PREFIX);
        Set<String> keys = opsHash.keys();
        if (null != keys && keys.size() > 0) {
            // 4_8, 使用正则匹配
            String regx = "\\d_" + skuId;
            for (String key : keys) {
                if (Pattern.matches(regx, key)) {
                    // 获取对应的商品秒杀信息
                    String s = opsHash.get(key);
                    SeckillSkuRedisTO seckillSkuRedisTO = JSON.parseObject(s, SeckillSkuRedisTO.class);
                    // 秒杀随机码根据当前商品是否处于秒杀时间，处于秒杀时间返回，不处于秒杀时间不返回秒杀随机码
                    long l = System.currentTimeMillis();

                    Long startTime = seckillSkuRedisTO.getStartTime();
                    Long endTime = seckillSkuRedisTO.getEndTime();

                    if (!(l >= startTime && l <= endTime)) {
                        seckillSkuRedisTO.setRandomCode(null);
                    }
                    return seckillSkuRedisTO;
                }
            }
        }
        return null;
    }

    // TODO 上架秒杀商品的时，每一个数据都有过期时间
    // TODO 秒杀后续的流程， 收货地址、运费计算等
    @Override
    public String kill(String killId, String code, Integer num) {
        // 获取登陆用户信息
        MemberRespVo memberRespVo = UserLoginInterceptor.local.get();

        // 获取秒杀商品的信息
        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKU_KEY_CACHE_PREFIX);
        //4_8
        String s = ops.get(killId);
        if (!StringUtils.isEmpty(s)) {
            // 缓存中商品的信息
            SeckillSkuRedisTO seckillSkuRedisTO = JSON.parseObject(s, SeckillSkuRedisTO.class);
            // 校验合法性
            long currL = System.currentTimeMillis();

            Long startTime = seckillSkuRedisTO.getStartTime();
            Long endTime = seckillSkuRedisTO.getEndTime();

            // 过期时间
            Long ttl = endTime - startTime;

            // 校验时间合法性
            if (currL >= startTime && currL <= endTime) {
                // 校验随机码，商品id
                String randomCode = seckillSkuRedisTO.getRandomCode();
//                Long skuId = seckillSkuRedisTO.getSkuId();
//                String redisSessionKey = seckillSkuRedisTO.getPromotionSessionId() + "_" + seckillSkuRedisTO.getSkuId();
                if (randomCode.equals(code)) {
                    // 检验商品的购买数量
                    Integer seckillLimit = seckillSkuRedisTO.getSeckillLimit();
                    if (seckillLimit.intValue() == num) {
                        // 幂等性处理
                        // 设置每一个用户只能购买一次商品，使用 redis的占位处理
                        //SETNX 占位，如果占位成功返回true、占位失败返回false，
                        // 设置key值
                        String setNX = memberRespVo.getId() + "_" + killId;
                        // 设置过期时间为 当前秒杀商品结束时间
                        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(setNX, num.toString(), ttl, TimeUnit.MILLISECONDS);
                        if (aBoolean) {
                            // 占位成功说明该用户是第一次购买
                            // 使用分布式信号量，来减库存
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                            // acquire是阻塞式的，不推荐使用
//                                semaphore.acquire(num);
//                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MICROSECONDS);
                            boolean b = semaphore.tryAcquire(num);
                            if (b) {

                                // 加库存成功，快速下单。发送消息到MQ
                                // 生成本次订单号
                                String orderSn = IdWorker.getTimeId();

                                SeckillOrderTO seckillOrderTO = new SeckillOrderTO();
                                seckillOrderTO.setMemberId(memberRespVo.getId());
                                seckillOrderTO.setNum(num);
                                seckillOrderTO.setOrderSn(orderSn);
                                seckillOrderTO.setPromotionSessionId(seckillSkuRedisTO.getPromotionSessionId());
                                seckillOrderTO.setSeckillPrice(seckillSkuRedisTO.getSeckillPrice());
                                seckillOrderTO.setSkuId(seckillSkuRedisTO.getSkuId());
                                // 发送消息到 MQ
                                rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTO);
                                return orderSn;
                            }
                            return null;
                        } else {
                            return null;
                        }
                    } else {
                        return null;
                    }
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }
        return null;
    }

    /**
     * 保存秒杀活动信息
     *
     * @param seckillSessionsWithSkusVO
     */
    private void saveSessionInfo(List<SeckillSessionsWithSkusVO> seckillSessionsWithSkusVO) {

        for (SeckillSessionsWithSkusVO sessionsWithSkusVO : seckillSessionsWithSkusVO) {

            // 活动开始时间毫秒值
            Long startTime = sessionsWithSkusVO.getStartTime().getTime();
            // 活动结束时间毫秒值
            Long endTime = sessionsWithSkusVO.getEndTime().getTime();

            // 拼接缓存前缀
            String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
            // 判断缓存中有没有此key，有则不再放入缓存中
            Boolean aBoolean = stringRedisTemplate.hasKey(key);
            if (aBoolean) {
                return;
            }
            // 所有关联的 商品id
            List<String> skuIds = sessionsWithSkusVO.getRelationEntities().stream().map(item -> {
                return item.getPromotionSessionId().toString() + "_" + item.getSkuId().toString();
            }).collect(Collectors.toList());
            // 放入缓存
            stringRedisTemplate.opsForList().leftPushAll(key, skuIds);
        }
    }

    /***
     *  保存活动关联的商品信息
     * @param seckillSessionsWithSkusVO
     */
    private void saveSessionSkuInfo(List<SeckillSessionsWithSkusVO> seckillSessionsWithSkusVO) {
        // 绑定一个Hash
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKU_KEY_CACHE_PREFIX);

        seckillSessionsWithSkusVO.forEach(item -> {
            item.getRelationEntities().stream().forEach(sku -> {
                // 商品随机码
                String token = UUID.randomUUID().toString().replace("-", "");

                // 判断该商品信息在缓存中已有，不在保存
                if (ops.hasKey(sku.getPromotionSessionId().toString() + "_" + sku.getSkuId().toString())) {
                    return;
                }

                SeckillSkuRedisTO seckillSkuRedisTO = new SeckillSkuRedisTO();
                // 缓存商品信息

                // sku基本信息
                R r = productFeignService.skuInfo(sku.getSkuId());
                if (r.getCode() == 0) {
                    SkuInfoVO data = r.getData("skuInfo", new TypeReference<SkuInfoVO>() {
                    });
                    // 存入TO中
                    seckillSkuRedisTO.setSkuInfoVO(data);
                }
                // sku秒杀信息
                BeanUtils.copyProperties(sku, seckillSkuRedisTO);
                // 商品商品秒杀时间
                seckillSkuRedisTO.setStartTime(item.getStartTime().getTime());
                seckillSkuRedisTO.setEndTime(item.getEndTime().getTime());

                // 商品随机码
                /***
                 *  设置商品随机码是为了防止 恶意攻击秒杀商品信息，导致商品库存不足
                 */
                seckillSkuRedisTO.setRandomCode(token);

                String jsonString = JSON.toJSONString(seckillSkuRedisTO);
                ops.put(sku.getPromotionSessionId().toString() + "_" + sku.getSkuId().toString(), jsonString);

                // 使用分布式redisson的信号量控制商品库存。 实现限流
                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                // 商品的秒杀数量作为信号量
                // 每次购买后，信号量值减1
                semaphore.trySetPermits(sku.getSeckillCount());
            });
        });
    }
}
